<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2017/5/18
 * Time: 16:45
 */

namespace app\admin\controller;

use think\Controller;
use think\Exception;
use think\Request;
use think\Session;
use think\Cache;
use \ReflectionClass;
use \ReflectionProperty;

class CommonController  extends Controller
{
    use \traits\controller\Decorate;
    use \traits\controller\CommonHandle;
    protected $listPage             =   10;     //  数据分页   条数
    protected $controller           =   "";     //  可手动指定当前的实例化的model这是全局的使用时需慎重
    protected $initModel            =   false;  //  是否实例化model
    protected $model                =   '';     //  实例化model
    const SUCCESS_CODE              =   1000;   //  请求失败的状态码
    const ERROR_CODE                =   1001;   //  请求失败返回的状态吗
    protected $session_id           =   '';     //  当前的session_id需要存redis 和 或者是数据库当中 但是是有过期时间
    const     RESPONSE_SUCCESS      =   200;    //   返回成功
    const     USER_DELETE_SUCCESS   =   204;    //  用户删除数据成功
    const     BAD_REQUEST_CODE      =   400;    //  请求方法不存在状态码
    const     NO_UNAUTHORIZED_CODE  =   401;    //  没有授权码AuthKey
    const     FORBIDDEN_CODE        =   403;    //  没有权限请求状态码
    const     NOT_FOUND_CODE        =   404;    //  请求的的页面不存在状态码
    const     METHOD_NOT_ALLOW      =   405;    //  请求方法不允许状态码
    const     CREATE_OBJ_ERROR      =   422;    //  创建对象错误
    const     BAD_GATEWAY_CODE      =   504;    //  服务器决绝请求状态码
    const     SERVER_ERROR_CODE     =   505;    //  服务器错误状态码 2xx Success  3xx Redirection 4xx Client Error 5xx Server Error
    protected $allow_field          =   [];     //  当前表允许访问方法的字段
    protected $index_allow_field    =   [];     //  列表白名单字段,可 以自定义过滤作用已过滤非法字段
    protected $save_allow_field     =   [];     //  添加时字段白名单  过滤作用已过滤非法字段
    protected $edit_allow_field     =   [];     //  修改时字段白名单  过滤作用已过滤非法字段  1xx Informational
    protected $fields               =   [];     //  所有白名单        都已生成的字段为准如新表字典需要重新生成所有字典节都待写
    protected $allowed_filter       =   [];     //  可用字段
    protected $allow_method         =   [
        'index'=>'GET',
        'save'=>'POST',
        'read'=>'GET',
        'update'=>'PUT',
        'delete'=>'DELETE'
    ];                                                                   //允许请求的方法 rest_full api http 动词
    protected $tmp_method           =   [];                              //临时请求的方法配置 否者只能请求以上的默认方法
    protected $is_strictly          =   false;                           //是否对参数进行严格验证 主要针对的是edit方法
    protected $action               =   '';                              //请求方法
    protected $method               =   '';
    protected $union_field          =   '';                             // 额外字段或者是关联插入字段
    protected $param                =   [];                             // 请求参数  当然可以自定义
    protected $custom_order         =   [];                             //自定义排序
    protected $header               =   [];                             //请求的header头
    protected $is_verify            =   true;
    protected $beforeHandle         =   [];
    protected $endHandle            =   [];

    /**
     * 注意
     * 服务端如果需要跑 swoole
     * 服务或者workman服务
     * 服务端那么我们不能继承公共类
     * 需要单独书写类以免导致进程退出
     */
    public function _initialize()
    {
        try{
            if(empty(config('needRequestMethod'))){throw new Exception('请求数据加载错误',self::BAD_GATEWAY_CODE);}
            $this->controller=empty($this->controller)? $this->request->controller():$this->controller;
            array_map(function($param){
                if(true===method_exists($this->request,$param))
                {
                    $this->$param    =  $this->request->$param();
                };
            },config('needRequestMethod'));
            $this->_check_all();
            //  注册装饰器 装饰器分为两类一类是公共decorate
            //  一类是模块decorate 公共一直存在其他的根据模块哦按需求是否加载
            $this->_checkDecorate();
        }catch (Exception $exception){
             header('HTTP/1.1 '.$exception->getCode());
             return $this->_error(['error'=>$exception->getMessage(),'code'=>$exception->getCode()],$exception->getCode());
        }
    }
    /**
     * @throws Exception
     */
    private function  _check_all(){
        //初始检查
        $initCheckMethod        =   config('initCHeckMethod');
        if(empty($initCheckMethod)){throw new Exception('抱歉应用初始化错误',self::BAD_REQUEST_CODE);}
        //用户uid常量 因为当前的UID在登录的时候会被设置如果再某些地方消除了常量UID那么这里会直接去Session里面的UID的自己加以重新设置
        if((!Session::has('UID') || false===defined('UID')) &&  null!==Session::get('UID') ){$this->_uid();}
        array_map(function($action){
            $checkMethod        =   method_exists($this,$action);
            if(true     === $checkMethod) call_user_func_array([$this,$action],[]);
        },$initCheckMethod);
    }

    /**
     * @return bool
     * 子类的属性必须可访问的属性 解决子类空值父类验证的属性的设置
     * 检查子类是否有自定义属性设置覆盖父类的属性值
     */
    private function _checkSonClassProperty(){
        $reflection      =           '';
        if(empty($reflection) || !($reflection instanceof ReflectionClass)) {
            $reflection  = new ReflectionClass(get_class($this));
        }
        $propertyAll     =           $reflection->getProperties(ReflectionProperty::IS_PUBLIC);
        if(empty($propertyAll)){return false;}
        array_map(function($property){
            $name        =           $property->getName();
            $this->$name =           $property->getValue($this);
        },$propertyAll);return       true;
    }
    /**
     * @return $this|bool
     * @throws Exception
     */
    private function  _checkAuth(){
           //登录不需要验证当前的authkey 如果要做sso的那么需要设置pso的回调地地址那么 可以使用我们yar rpc 或者workerman rpc
           //或者我们的hporse rpc 结合我们kerberos 认证来做我们的cookie同步 但需要我们的rsa加密处理对称加密所以密钥必须是每个
           //服务器上都要部署 可以结合我们rsync 集群同步做我们的 也可以使用yar rpc 做我们的并行处理 已达到各个系统延迟秒数
            if(strcmp($this->action,'login')==0 || $this->is_verify===false){return true;}
             if(!isset($this->header['authkey'])){throw new Exception('抱歉授权错误',self::NO_UNAUTHORIZED_CODE);}
             $requestAuthKey      =$this->header['authkey'];
            if(empty($requestAuthKey)){throw new Exception('抱歉授权错误！',self::NO_UNAUTHORIZED_CODE);}
             $Check_result        = checkAuthKey($this->header['authkey']);
            if(false===$Check_result){throw new Exception('抱歉您没有权限！',self::FORBIDDEN_CODE);}
            return $this;
    }

    /**
     * @throws Exception
     */
    private   function _init_model(){
        if (true == $this->initModel) {
            //引用命名空间
            $space          = config('app_namespace');
            //模块名称
            $module         = $this->request->module();
            //模块路径
            $model          = vsprintf($space."%s".$module.'%s'.ucfirst($this->controller),[chr(92),chr(92).'model'.chr(92)]);
            //判断是否存在模型
            $modelExist     = true == class_exists($model) ? true : false;
            if (true == $modelExist) {
                $this->model = model($this->controller);
            } else {throw new Exception('模型初始化错误！',self::CREATE_OBJ_ERROR);}
        }
    }

    /**
     * @throws Exception
     * @use    验证访问的方法是否在访问列表中
     */
    private function _checkMethod(){
        /*$obj                =   $this;$flag=true;
        // 定义匿名函数变量进行验证
        $validate           =   function($method_name)use($obj){
            //加上临时的方法如果不设置那么默认就是当前的允许的5个rest full api的方法
            if(!empty($obj->tmp_method)){
                $obj->allow_method    = array_merge($obj->allow_method,$obj->tmp_method);
            }
            if(!in_array($obj->action,$obj->allow_method) || strtoupper($obj->method)!==$method_name)
            {return  false;}return true;
        };
        switch(strtolower($this->action))
        {
            case 'index'    :$flag=$validate('GET');break;
            case 'save'     :$flag=$validate('POST');break;
            case 'read'     :$flag=$validate('GET');break;
            case 'update'   :$flag=$validate('PUT');break;
            case 'delete'   :$flag=$validate('DELETE');break;
            default:
                $flag             =$validate('POST'); //其他的必须是post请求
        }*/
        /*也可以设置header头来限制请求的方式
            header("Access-Control-Allow-Origin: *");
            header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
            header("Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE"); 但是限制不够灵活
        */
        //加上临时的方法如果不设置那么默认就是当前的允许的5个rest full api的方法
        if(!empty($this->tmp_method)){
            $this->allow_method     =   array_merge($this->allow_method,$this->tmp_method);
        }
        $request_action             =   array_keys($this->allow_method);
        $flag                       =   true;
        if(!in_array($this->action,$request_action) || !isset($this->allow_method[$this->action])
            || strtolower($this->method)!==strtolower($this->allow_method[$this->action]))
        {
            $flag=false;
        }
        if($flag===false){throw new Exception('抱歉请求错误',self::BAD_REQUEST_CODE);}
    }
    /**
     * 设置 uid 定义常量
     * 生成当前客户端session_id
     * 实现session共享
     */
    private function _uid(){
        define('UID',Session::get('UID'));
    }
    /**
     * 全局的list页面如需其他处理可更改
     */
    public function index()
    {
        try{
            $where                   =$endWhere =    $this->param;
            $this->beforeHandle['_beforeWhere'] =    $where;
            $this->_beforeDecorate();
            $list                    =           $this->model->lists($this->beforeHandle['_beforeWhere'],$this->index_allow_field,$this->custom_order,$this->listPage);
            if(empty($list)){throw new Exception($this->model->getError(),self::RESPONSE_SUCCESS);}

            $this->endHandle['_beforeList']     =    $list;
            $this->endHandle['_endWhere']       =    $endWhere;
            $this->_endDecorate();
            return $this->_success($this->endHandle['_beforeList'],'小惠已为您找到可用数据！');
        }catch (Exception $exception){
            return $this->_error(['data'=>'','status'=>self::ERROR_CODE,'info'=>$exception->getMessage()],$exception->getCode());
        }
    }
    /**
     * 保存新建的资源 修改的时候保存的我们的资源
     * @param  \think\Request $request
     * @return \think\Response
     */
    public function save()
    {
        try{
            $create_data            =   $params=$this->param;
            $where                  =           [];
            $this->beforeHandle['_beforeWhere']=$where;
            $this->beforeHandle['_beforeParam']=$create_data;
            $this->_beforeDecorate();
            $result                 =           $this->model->addDb($this->beforeHandle['_beforeParam'],$this->save_allow_field, $this->beforeHandle['_beforeWhere']);
            if(false===$result){throw  new Exception($this->model->getError(),self::RESPONSE_SUCCESS);}
            $this->endHandle['_endParam']=$params;
            $this->endHandle['_endWhere']=$where;
            $this->_endDecorate();
            return $this->_success($this->endHandle['_endParam'],'小惠已经为您添加成功....');
        }catch (Exception $exception){
           $data= ['data'=>'','status'=>self::ERROR_CODE,'info'=>$exception->getMessage()];$code=$exception->getCode();
           return $this->_error($data,$code);
        }
    }
    /**
     * 显示编辑资源表单页.
     * @param  int $id
     * @return \think\Response
     */
    public function edit($id)
    {
        try{
            if(!is_numeric($id) || empty($id)){throw new Exception('抱歉缺少参数！',self::BAD_REQUEST_CODE);}
            $this->_beforeDecorate();
            $data               =   $this->model->read($id,$this->edit_allow_field);
            if(false===$data){throw new Exception($this->model->getError(),self::RESPONSE_SUCCESS);}
            $this->endHandle['_beforeEdit']=$data;
            $this->_endDecorate();
            return $this->_success($this->endHandle['_beforeEdit'],'小惠已为您找到您需要的了。。。。。！');
        }catch (Exception $exception){
            $data= ['data'=>'','status'=>self::ERROR_CODE,'info'=>$exception->getMessage()];$code=$exception->getCode();
            return $this->_error($data,$code);
        }
    }

    /**
     * 保存更新的资源
     * @param Request $request
     * @return \think\response\Json
     */
    public function update(Request $request)
    {
        try {
                $param  = $endParam = $endWhere= $where = $this->param;
                $this->beforeHandle['_beforeParam']=$param;
                $this->beforeHandle['_beforeWhere']=$where;
                $this->_beforeDecorate();
                $result = $this->model->addDb($this->beforeHandle['_beforeParam'], $this->edit_allow_field, $this->beforeHandle['_beforeWhere']);
                if(false===$result){throw new Exception($this->model->getError(),self::RESPONSE_SUCCESS);}
                $this->endHandle['_endWhere']=$endWhere;
                $this->endHandle['_endParam']=$endParam;
                $this->_endDecorate();
                return $this->_success($this->endHandle['_endParam'], '小惠已为您处理成功。。。。。！');
        }catch (Exception $exception){
            $data= ['data'=>'','status'=>self::ERROR_CODE,'info'=>$exception->getMessage()];$code=$exception->getCode();
            return $this->_error($data,$code);
        }
    }
    /**
     * 删除指定资源
     * @param  int $id
     * @return \think\Response
     */
    public function delete($id){}
    /**
     * 显示指定的资源相当于详情页面
     * @param  int $id
     * @return \think\Response
     */
    public function read($id){}
    //成功时返回的
    public function         _success($data,$message,$code=self::RESPONSE_SUCCESS)
    {
        //这里实现session_id共享 当然我们的session 是有过期时间的
        $this->_session_create();
        $authKey                        =               $this->_uuid();
        //这个可以生成
        return json(['list'=>$data,'status'=>self::SUCCESS_CODE,'info'=>$message],$code,['authKey'=>$authKey,'sessionID'=>$this->session_id]);
    }
    /**
     * @param $data
     * @param $code
     * @return \think\response\Json
     */
    protected function _error($data,$code)
    {
        $authKey                        =               $this->_uuid();
        //这里实现session_id共享 当然我们的session 是有过期时间的
        $this->_session_create();
        return json($data,$code,['authKey'=>$authKey,'sessionID'=>$this->session_id]);
    }
    /**
     * 可重写但不建议
     */
    protected function _filterParam()
    {
        //>>        检查是去掉我们的严格验证   加入我们需要链表搜索或者链表插入可不验证视情况而定可灵活的家或者减
        $flag    =   true;$this->allow_field = config('column.'.strtolower($this->controller));
       $obj                             =   $this;
       $validate                        =   function($param,$action)use($obj,&$flag){
           $action                      =   strtolower($action);
           $param_key                   =   array_diff(array_keys($param),array_keys($obj->allow_field[$action]));
           // 是否需要严格的验证插入字段
           if(true===$obj->is_strictly) {
               if (!empty($param_key)) {
                   array_map(function ($element) use (&$param) {
                       unset($param[$element]);
                   }, $param_key);
               };
               $flag = sizeof($param) == sizeof($obj->allow_field[$action])? true : false;
           }
           // 过滤数据库不存在的参数 单表过滤,在严格过滤的情况下
           $obj->param                  =   $param;
           return implode(array_values($obj->allow_field[$action]),',');
       };
       //  允许读取的字段白名单，当然修改后可重新生成可访问字段,也可自定义 后台追主要就是这几个方法 还有update方法
       switch(strtolower($this->action)){
           case 'index':$this->index_allow_field=implode(array_values($obj->allow_field[$this->action]),',');
           break;
           case 'update':case 'save':
           $this->save_allow_field   =$validate($this->param,'save');
           break;
           case 'edit':$this->edit_allow_field  =$validate($this->param,'edit');
           break;
       }
       if($flag===false){throw new Exception('缺少请求参数',self::BAD_REQUEST_CODE);}
    }
    /**
     *  创建的session_id
     */
    private function _session_create(){
        //在不改变数据的情况下更改当前的session_id session_regenerate_id();传入true会删除原来的session_id 对应的值
        session_regenerate_id();
        $session_id             =   session_id();
        $this->session_id       =   empty($session_id)?'':$session_id;

    }
    /**
     * 需要显示的去生成我们的uuid以便验证安全
     */
    private function _uuid()
    {
            // 缓存当前的的授权   ID 加上当前的 登录之后加上当前的授权id token survive生存时间
            $uuid               =uuid();
            $uuid_cache_key     ="authKey_".UID;
            Cache::set($uuid_cache_key,$uuid,config('authKey'));
            return $uuid;
    }

    /**
     * @return mixed|string|\think\response\Json
     * 获取 es7 async await 请求
     */
    protected function _fetch_param()
    {
        try {
            //获取请求出入流  es7 的 fetch api 需要
            $data = file_get_contents('php://input');
            $data = json_decode($data, true);
            if (true === empty($data)) {throw new Exception('抱歉参数错误不能为空！', self::BAD_REQUEST_CODE);}
            return $data;
        } catch (Exception $e) {
            return json(['data' => '', 'code' => self::BAD_REQUEST_CODE, 'error' => $e->getMessage()], $e->getCode());
        }
    }
}